// ==UserScript==
// @name         DeepLearning.AI 智能字幕双语显示
// @namespace    http://tampermonkey.net/
// @version      6.0
// @description  智能提取字幕，后台翻译，最小化UI设计，边看边译
// @author       AI Assistant
// @match        https://learn.deeplearning.ai/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @connect      api.deepseek.com
// @connect      api.mymemory.translated.net
// @connect      translate.googleapis.com
// @connect      *
// @require      https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js
// ==/UserScript==

(function() {
    'use strict';

    // 防止重复初始化
    if (window.DL_SUBTITLE_TRANSLATOR_LOADED) {
        console.log('⚠️ 字幕翻译器已加载，跳过重复初始化');
        return;
    }
    window.DL_SUBTITLE_TRANSLATOR_LOADED = true;
    console.log('✅ 字幕翻译器开始加载...');

    GM_addStyle(`
        .bilingual-subtitle-overlay {
            position: fixed;
            bottom: 100px;
            left: 50%;
            transform: translateX(-50%);
            z-index: 99999;
            max-width: 90%;
            background: linear-gradient(135deg, rgba(0, 0, 0, 0.95) 0%, rgba(20, 20, 40, 0.95) 100%);
            border-radius: 16px;
            padding: 20px 32px;
            box-shadow: 0 12px 48px rgba(0, 0, 0, 0.8), 0 0 0 1px rgba(255, 255, 255, 0.1);
            backdrop-filter: blur(20px);
            /* allow interaction so user can drag the overlay */
            pointer-events: auto;
            cursor: grab;
            user-select: none;
        }

        /* removed overlay entrance animation to avoid flicker on updates */

        .subtitle-en {
            color: #e0e0e0;
            font-size: 17px;
            line-height: 1.7;
            margin-bottom: 10px;
            text-align: center;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
        }

        .subtitle-cn {
            color: #ff6b6b;
            font-size: 19px;
            font-weight: 600;
            line-height: 1.7;
            text-align: center;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Microsoft YaHei", sans-serif;
        }

        .subtitle-control-panel {
            position: fixed;
            top: 100px;
            right: 40px;
            left: auto;
            bottom: auto;
            z-index: 99998;
            background: linear-gradient(135deg, rgba(0, 0, 0, 0.9) 0%, rgba(30, 30, 50, 0.9) 100%);
            border-radius: 16px;
            padding: 20px 24px;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(255, 255, 255, 0.1);
            backdrop-filter: blur(10px);
            transition: all 0.3s ease;
            display: flex;
            flex-direction: column;
            align-items: flex-start;
            gap: 10px;
            min-width: 220px;
            max-width: 320px;
        }
        .subtitle-control-panel.minimized {
            width: 48px;
            height: 48px;
            padding: 6px;
            border-radius: 12px;
            align-items: center;
            justify-content: center;
            gap: 0;
        }
        .subtitle-control-panel.minimized .control-title,
        .subtitle-control-panel.minimized .subtitle-count,
        .subtitle-control-panel.minimized .progress-bar,
        .subtitle-control-panel.minimized .status-text {
            display: none;
        }
        .panel-small-btn {
            width: 36px;
            height: 36px;
            padding: 6px;
            border-radius: 8px;
            background: rgba(255,255,255,0.06);
            color: #fff;
            border: none;
            cursor: pointer;
            display: inline-flex;
            align-items: center;
            justify-content: center;
        }
        /* make control panel draggable */
        .subtitle-control-panel {
            cursor: grab;
        }

        .subtitle-control-panel.expanded {
            border-radius: 16px;
            padding: 20px;
            flex-direction: column;
            align-items: stretch;
            width: 400px;
            max-width: 90%;
        }

        .subtitle-control-panel.minimized {
            padding: 8px 20px;
        }

        .control-title {
            color: #fff;
            font-size: 16px;
            font-weight: bold;
            margin-bottom: 16px;
            text-align: center;
            border-bottom: 2px solid rgba(255, 255, 255, 0.1);
            padding-bottom: 10px;
            display: none;
        }

        .subtitle-control-panel.expanded .control-title {
            display: block;
        }

        .toggle-panel-btn {
            background: rgba(255, 255, 255, 0.2);
            border: none;
            color: white;
            padding: 6px 12px;
            border-radius: 20px;
            cursor: pointer;
            font-size: 12px;
            font-weight: 600;
            transition: all 0.3s ease;
            white-space: nowrap;
        }

        .toggle-panel-btn:hover {
            background: rgba(255, 255, 255, 0.3);
            transform: scale(1.05);
        }

        .status-indicator {
            display: flex;
            align-items: center;
            gap: 8px;
            font-size: 13px;
            color: #fff;
            white-space: nowrap;
        }

        .status-dot {
            width: 8px;
            height: 8px;
            border-radius: 50%;
            background: #4ade80;
            animation: pulse 2s ease-in-out infinite;
        }

        @keyframes pulse {
            0%, 100% { opacity: 1; }
            50% { opacity: 0.5; }
        }

        .mini-controls {
            display: flex;
            align-items: center;
            gap: 10px;
        }

        .subtitle-control-panel.expanded .mini-controls {
            display: none;
        }

        .full-controls {
            display: none;
        }

        .subtitle-control-panel.expanded .full-controls {
            display: flex;
            flex-direction: column;
        }

        .control-btn {
            width: 100%;
            padding: 12px 18px;
            margin: 8px 0;
            border: none;
            border-radius: 10px;
            font-size: 14px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s ease;
            color: white;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
            display: block;
        }

        .subtitle-control-panel.minimized .control-btn {
            display: none;
        }

        .control-btn.primary {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        }

        .control-btn.success {
            background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
        }

        .control-btn.warning {
            background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
        }

        .control-btn.danger {
            background: linear-gradient(135deg, #eb3349 0%, #f45c43 100%);
        }

        .control-btn:hover:not(:disabled) {
            transform: translateY(-3px);
            box-shadow: 0 6px 20px rgba(102, 126, 234, 0.5);
        }

        .control-btn:active:not(:disabled) {
            transform: translateY(-1px);
        }

        .control-btn:disabled {
            opacity: 0.5;
            cursor: not-allowed;
            transform: none !important;
        }

        .status-text {
            color: #b0b0b0;
            font-size: 13px;
            text-align: center;
            margin-top: 12px;
            line-height: 1.5;
            min-height: 40px;
        }

        .progress-bar {
            width: 100%;
            height: 6px;
            background: rgba(255, 255, 255, 0.1);
            border-radius: 3px;
            margin: 12px 0;
            overflow: hidden;
            display: block;
        }

        .subtitle-control-panel.minimized .progress-bar {
            display: none;
        }

        .progress-fill {
            height: 100%;
            background: linear-gradient(90deg, #667eea 0%, #764ba2 50%, #f093fb 100%);
            transition: width 0.3s ease;
            width: 0%;
            box-shadow: 0 0 10px rgba(102, 126, 234, 0.5);
        }

        .loading-spinner {
            display: inline-block;
            width: 16px;
            height: 16px;
            border: 3px solid rgba(255, 255, 255, 0.3);
            border-top-color: #fff;
            border-radius: 50%;
            animation: spin 0.8s linear infinite;
            vertical-align: middle;
            margin-right: 8px;
        }

        @keyframes spin {
            to { transform: rotate(360deg); }
        }

        .subtitle-count {
            background: rgba(102, 126, 234, 0.2);
            color: #667eea;
            padding: 6px 12px;
            border-radius: 8px;
            font-size: 12px;
            text-align: center;
            margin: 8px 0;
            font-weight: 600;
            display: block;
        }

        .subtitle-control-panel.minimized .subtitle-count {
            display: none;
        }
    `);

    class TranscriptPanelExtractor {
        constructor() {
            this.enabled = false;
            this.subtitles = [];
            this.sentences = [];
            this.currentIndex = -1;
            this.videoElement = null;
            this.isTranslating = false;
            this.observer = null;
            this.slidingIntervalId = null; // id for sliding-window translator
            this.slidingWindowSize = 6; // how many upcoming subtitles to prefetch
            this.prefetchDelay = 800; // ms between each translation request

            this.init();
        }

        async init() {
            console.log('🚀 DeepLearning.AI 字幕翻译器启动...');

            this.createControlPanel();
            await this.loadCache();
            this.waitForVideo();

            // 自动点击 "Show transcript panel" 按钮
            setTimeout(() => this.autoOpenTranscriptPanel(), 2000);
        }

        createControlPanel() {
            const panel = $(`
                <div class="subtitle-control-panel">
                    <div style="display:flex; width:100%; justify-content:space-between; align-items:center;">
                        <div class="control-title">📝 字幕翻译助手</div>
                        <div style="display:flex; gap:8px; align-items:center;">
                            <button class="panel-small-btn" id="btn-hide-subtitles" title="隐藏/显示字幕">👁️</button>
                            <button class="panel-small-btn" id="btn-settings" title="设置">⚙️</button>
                            <button class="panel-small-btn" id="btn-collapse" title="收起/展开面板">━</button>
                        </div>
                    </div>
                    <button class="control-btn success" id="btn-extract">
                        🔍 提取字幕
                    </button>
                    <div class="subtitle-count" id="subtitle-count" style="display:none;">
                        已提取: 0 条字幕
                    </div>
                    <button class="control-btn warning" id="btn-toggle" disabled>
                        ▶️ 开启双语显示
                    </button>
                    <button class="control-btn danger" id="btn-clear">
                        🗑️ 清除缓存
                    </button>
                    <div class="progress-bar">
                        <div class="progress-fill" id="progress-fill"></div>
                    </div>
                    <div class="status-text" id="status-text">
                        等待操作...
                    </div>
                </div>
            `);

            try {
                // Prefer jQuery append when panel is a jQuery object
                if (panel && panel.jquery) {
                    $('body').append(panel);
                } else if (typeof panel === 'string') {
                    // If somehow panel became a raw HTML string, convert to element
                    const wrapper = document.createElement('div');
                    wrapper.innerHTML = panel;
                    document.body.appendChild(wrapper);
                } else if (panel && panel.nodeType) {
                    document.body.appendChild(panel);
                } else {
                    // Fallback: attempt to create minimal panel container so UI still appears
                    console.warn('createControlPanel: unexpected panel type, creating fallback container', panel);
                    const fallback = document.createElement('div');
                    fallback.className = 'subtitle-control-panel';
                    document.body.appendChild(fallback);
                }
            } catch (err) {
                console.error('Append control panel failed:', err, panel);
                // Ensure script continues even if UI can't be attached
                try {
                    const fallback = document.createElement('div');
                    fallback.className = 'subtitle-control-panel';
                    document.body.appendChild(fallback);
                } catch (e) {
                    console.error('Fallback append also failed:', e);
                }
            }

            $('#btn-extract').click(() => this.extractSubtitles());
            $('#btn-toggle').click(() => this.toggleDisplay());
            $('#btn-clear').click(() => this.clearCache());
            $('#btn-hide-subtitles').click(() => {
                this.subtitlesVisible = !this.subtitlesVisible;
                const ov = document.querySelector('.bilingual-subtitle-overlay');
                if (ov) {
                    ov.style.display = this.subtitlesVisible && this.enabled ? 'block' : 'none';
                }
                $('#btn-hide-subtitles').text(this.subtitlesVisible ? '👁️' : '🙈');
            });

            // Settings: set DeepSeek API key and prefetch params
            $('#btn-settings').click(() => {
                const current = GM_getValue('deepseek_api_key') || '';
                const key = prompt('请输入 DeepSeek API Key (将保存在本地)：', current);
                if (key !== null) {
                    GM_setValue('deepseek_api_key', key.trim());
                    this.updateStatus(key.trim() ? '✅ 已保存 DeepSeek API Key' : '⚠️ 已清除 DeepSeek API Key');
                }

                // allow adjusting window size and delay
                const ws = prompt('预翻译句子数 (slidingWindowSize)：', this.slidingWindowSize);
                if (ws !== null) {
                    const n = parseInt(ws);
                    if (!isNaN(n) && n > 0) this.slidingWindowSize = n;
                }
                const pd = prompt('每次请求延迟 (ms, prefetchDelay)：', this.prefetchDelay);
                if (pd !== null) {
                    const m = parseInt(pd);
                    if (!isNaN(m) && m >= 0) this.prefetchDelay = m;
                }
            });

            $('#btn-collapse').click(() => {
                const panelEl = document.querySelector('.subtitle-control-panel');
                if (!panelEl) return;
                panelEl.classList.toggle('minimized');
                const isMin = panelEl.classList.contains('minimized');
                $('#btn-collapse').text(isMin ? '▸' : '━');
            });

            // Make panel draggable by mouse
            const el = document.querySelector('.subtitle-control-panel');
            if (el) {
                let isDragging = false;
                let startX = 0, startY = 0, startLeft = 0, startTop = 0;

                const onMouseDown = (e) => {
                    // only left button
                    if (e.button !== 0) return;
                    // don't start drag when clicking buttons or interactive elements
                    if (e.target.closest && e.target.closest('button, a, input, textarea, select')) return;
                    isDragging = true;
                    el.style.transition = 'none';
                    startX = e.clientX;
                    startY = e.clientY;
                    const rect = el.getBoundingClientRect();
                    // switch to left/top positioning for dragging
                    el.style.left = rect.left + 'px';
                    el.style.top = rect.top + 'px';
                    el.style.right = 'auto';
                    el.style.bottom = 'auto';
                    startLeft = rect.left;
                    startTop = rect.top;
                    document.addEventListener('mousemove', onMouseMove);
                    document.addEventListener('mouseup', onMouseUp);
                    e.preventDefault();
                };

                const onMouseMove = (e) => {
                    if (!isDragging) return;
                    const dx = e.clientX - startX;
                    const dy = e.clientY - startY;
                    el.style.left = (startLeft + dx) + 'px';
                    el.style.top = (startTop + dy) + 'px';
                };

                const onMouseUp = (e) => {
                    if (!isDragging) return;
                    isDragging = false;
                    el.style.transition = ''; // restore
                    document.removeEventListener('mousemove', onMouseMove);
                    document.removeEventListener('mouseup', onMouseUp);
                };

                el.addEventListener('mousedown', onMouseDown);
            }
        }

        // Ensure the subtitle overlay exists; create it once and reuse
        ensureOverlayExists() {
            let overlay = document.querySelector('.bilingual-subtitle-overlay');
            if (!overlay) {
                overlay = document.createElement('div');
                overlay.className = 'bilingual-subtitle-overlay';
                overlay.style.display = 'none';
                overlay.innerHTML = `<div class="subtitle-en"></div><div class="subtitle-cn"></div>`;
                document.body.appendChild(overlay);
                // make overlay draggable
                if (!overlay._draggableAdded) {
                    overlay._draggableAdded = true;
                    let isDragging = false;
                    let startX = 0, startY = 0, startLeft = 0, startTop = 0;

                    const onMouseDown = (e) => {
                        // only left button
                        if (e.button !== 0) return;
                        isDragging = true;
                        overlay.style.transition = 'none';
                        overlay.style.cursor = 'grabbing';
                        startX = e.clientX;
                        startY = e.clientY;
                        const rect = overlay.getBoundingClientRect();
                        // switch to left/top positioning for dragging
                        overlay.style.left = rect.left + 'px';
                        overlay.style.top = rect.top + 'px';
                        overlay.style.right = 'auto';
                        overlay.style.bottom = 'auto';
                        startLeft = rect.left;
                        startTop = rect.top;
                        document.addEventListener('mousemove', onMouseMove);
                        document.addEventListener('mouseup', onMouseUp);
                        e.preventDefault();
                    };

                    const onMouseMove = (e) => {
                        if (!isDragging) return;
                        const dx = e.clientX - startX;
                        const dy = e.clientY - startY;
                        overlay.style.left = (startLeft + dx) + 'px';
                        overlay.style.top = (startTop + dy) + 'px';
                    };

                    const onMouseUp = (e) => {
                        if (!isDragging) return;
                        isDragging = false;
                        overlay.style.transition = '';
                        overlay.style.cursor = 'grab';
                        document.removeEventListener('mousemove', onMouseMove);
                        document.removeEventListener('mouseup', onMouseUp);
                    };

                    overlay.addEventListener('mousedown', onMouseDown);
                }
            }
            return overlay;
        }

        updateMiniStatus(text, showDot = false) {
            $('#mini-status').text(text);
            if (showDot) {
                $('#status-dot').show();
            } else {
                $('#status-dot').hide();
            }
        }

        updateStatus(text, spinner = false) {
            const html = spinner ? `<span class="loading-spinner"></span>${text}` : text;
            $('#status-text').html(html);
        }

        updateProgress(percent) {
            $('#progress-fill').css('width', percent + '%');
        }

        showDebugInfo() {
            const info = [];

            // 检查视频元素
            const video = document.querySelector('video');
            info.push(`视频元素: ${video ? '✅ 已找到' : '❌ 未找到'}`);

            // 检查字幕数量
            info.push(`已提取字幕: ${this.subtitles.length} 条`);

            // 检查各种选择器
            const selectors = [
                'button[class*="link"]',
                '.flex.p-2.gap-3.items-start',
                '[class*="flex"][class*="gap-3"]',
                'span'
            ];

            info.push('<br><strong>DOM 元素检查:</strong>');
            selectors.forEach(selector => {
                const elements = document.querySelectorAll(selector);
                info.push(`${selector}: ${elements.length} 个`);
            });

            // 显示前3条字幕
            if (this.subtitles.length > 0) {
                info.push('<br><strong>前3条字幕:</strong>');
                this.subtitles.slice(0, 3).forEach((sub, i) => {
                    info.push(`${i + 1}. [${sub.startTime}s] ${sub.en.substring(0, 50)}...`);
                });
            }

            $('#debug-content').html(info.join('<br>'));
        }

        waitForVideo() {
            const check = setInterval(() => {
                const video = document.querySelector('video');
                if (video) {
                    clearInterval(check);
                    this.videoElement = video;
                    console.log('✅ 视频元素已找到');
                    this.updateStatus('✅ 视频已加载，可以提取字幕');
                }
            }, 500);
        }

        autoOpenTranscriptPanel() {
            // 查找 "Show transcript panel" 按钮
            const button = Array.from(document.querySelectorAll('button')).find(btn =>
                btn.textContent.includes('Show transcript panel') ||
                btn.getAttribute('aria-label')?.includes('Show transcript panel')
            );

            if (button) {
                console.log('✅ 找到 transcript panel 按钮，自动打开...');
                button.click();
                this.updateStatus('✅ 已自动打开字幕面板');

                // 等待面板加载后自动提取
                setTimeout(() => this.extractSubtitles(), 1500);
            } else {
                console.log('⚠️ 未找到 transcript panel 按钮');
                this.updateStatus('请手动点击"Show transcript panel"');
            }
        }

        extractSubtitles() {
            this.updateStatus('正在提取字幕...', true);
            console.log('🔍 开始提取字幕...');

            try {
                // 方法1: 精确查找字幕项
                console.log('方法1: 查找字幕项...');
                const selectors = [
                    '.flex.p-2.gap-3.items-start',
                    'div[class*="flex"][class*="gap-3"][class*="items-start"]',
                    'div.flex-1.overflow-y-auto > div > div'
                ];

                for (const selector of selectors) {
                    const items = document.querySelectorAll(selector);
                    console.log(`  尝试选择器 "${selector}": 找到 ${items.length} 个元素`);

                    if (items.length > 5) {
                        this.parseTranscriptItems(items);
                        if (this.subtitles.length > 0) {
                            console.log(`✅ 使用选择器 "${selector}" 成功提取字幕`);
                            break;
                        }
                    }
                }

                // 方法2: 从视频的 textTracks 提取
                if (this.subtitles.length === 0 && this.videoElement) {
                    console.log('方法2: 从 textTracks 提取...');
                    this.extractFromTextTracks();
                }

                // 方法3: 等待字幕面板加载
                if (this.subtitles.length === 0) {
                    console.log('方法3: 等待字幕面板加载...');
                    this.observeTranscriptPanel();
                    return;
                }

                if (this.subtitles.length > 0) {
                    this.onExtractSuccess();
                } else {
                    this.updateStatus('❌ 未找到字幕，请确保已打开 transcript panel');
                    console.log('❌ 所有方法都未能提取到字幕');
                }

            } catch (error) {
                console.error('❌ 提取字幕失败:', error);
                this.updateStatus('❌ 提取失败: ' + error.message);
            }
        }

        parseTranscriptItems(items) {
            this.subtitles = [];
            console.log(`📝 开始解析 ${items.length} 个字幕项...`);

            items.forEach((item, index) => {
                // 打印前3个元素的结构用于调试
                if (index < 3) {
                    console.log(`  项 ${index} HTML:`, item.outerHTML.substring(0, 300));
                }

                // 查找时间戳按钮
                const timeButton = item.querySelector('button[class*="link"]');
                const textSpan = item.querySelector('span');

                if (index < 3) {
                    console.log(`  项 ${index} - timeButton:`, timeButton ? '✓' : '✗', 'textSpan:', textSpan ? '✓' : '✗');
                }

                if (timeButton && textSpan) {
                    const timeText = timeButton.textContent.trim(); // 例如: "0:00"
                    const subtitleText = textSpan.textContent.trim();

                    if (index < 3) {
                        console.log(`  项 ${index} - 时间: "${timeText}", 文本: "${subtitleText}"`);
                    }

                    if (subtitleText) {
                        const startTime = this.parseTimeToSeconds(timeText);

                        this.subtitles.push({
                            index: index,
                            startTime: startTime,
                            endTime: startTime + 5, // 默认5秒，后续会调整
                            en: subtitleText,
                            cn: null
                        });
                    }
                }
            });

            // 调整结束时间
            for (let i = 0; i < this.subtitles.length - 1; i++) {
                this.subtitles[i].endTime = this.subtitles[i + 1].startTime;
            }

            console.log(`✅ 从 DOM 提取了 ${this.subtitles.length} 条字幕`);
        }

        parseTimeToSeconds(timeStr) {
            // 将 "0:00" 或 "1:23" 转换为秒数
            const parts = timeStr.split(':');
            if (parts.length === 2) {
                return parseInt(parts[0]) * 60 + parseInt(parts[1]);
            } else if (parts.length === 3) {
                return parseInt(parts[0]) * 3600 + parseInt(parts[1]) * 60 + parseInt(parts[2]);
            }
            return 0;
        }

        extractFromTextTracks() {
            if (!this.videoElement || !this.videoElement.textTracks) return;

            const tracks = this.videoElement.textTracks;
            for (let i = 0; i < tracks.length; i++) {
                const track = tracks[i];
                if (track.kind === 'subtitles' || track.kind === 'captions') {
                    track.mode = 'hidden';

                    if (track.cues && track.cues.length > 0) {
                        this.subtitles = Array.from(track.cues).map((cue, index) => ({
                            index: index,
                            startTime: cue.startTime,
                            endTime: cue.endTime,
                            en: cue.text.replace(/<[^>]*>/g, ''),
                            cn: null
                        }));

                        console.log(`✅ 从 textTracks 提取了 ${this.subtitles.length} 条字幕`);
                        break;
                    }
                }
            }
        }

        observeTranscriptPanel() {
            this.updateStatus('等待字幕面板加载...', true);
            console.log('⏳ 开始等待字幕面板加载...');

            let checkCount = 0;
            const maxChecks = 30; // 最多检查30次（15秒）

            const checkInterval = setInterval(() => {
                checkCount++;
                console.log(`🔍 检查次数: ${checkCount}/${maxChecks}`);

                // 尝试多种选择器
                const selectors = [
                    'button[class*="link"]', // 时间戳按钮
                    '.flex.p-2.gap-3.items-start',
                    '[class*="flex"][class*="gap-3"]'
                ];

                for (const selector of selectors) {
                    const items = document.querySelectorAll(selector);

                    if (items.length > 5) {
                        console.log(`✅ 找到 ${items.length} 个元素，使用选择器: ${selector}`);

                        // 如果找到的是按钮，需要找到它们的父容器
                        let transcriptItems;
                        if (selector.includes('button')) {
                            // 找到所有包含时间戳按钮的父元素
                            const parents = new Set();
                            items.forEach(btn => {
                                const parent = btn.closest('.flex.p-2, [class*="flex"][class*="gap"]');
                                if (parent) parents.add(parent);
                            });
                            transcriptItems = Array.from(parents);
                        } else {
                            transcriptItems = items;
                        }

                        if (transcriptItems.length > 0) {
                            this.parseTranscriptItems(transcriptItems);

                            if (this.subtitles.length > 0) {
                                clearInterval(checkInterval);
                                this.onExtractSuccess();
                                return;
                            }
                        }
                    }
                }

                if (checkCount >= maxChecks) {
                    clearInterval(checkInterval);
                    this.updateStatus('❌ 超时：未能提取字幕，请手动点击提取按钮');
                    console.log('❌ 已尝试 30 次，未找到字幕');
                }
            }, 500);
        }

        onExtractSuccess() {
            console.log('✅ 字幕提取成功:', this.subtitles);

            $('#subtitle-count').text(`已提取: ${this.subtitles.length} 条字幕`).show();
            // 将连续的字幕合并为句子，按句子进行翻译与展示
            this.groupSubtitlesIntoSentences();

            this.updateStatus(`✅ 成功提取 ${this.subtitles.length} 条字幕 (${this.sentences.length} 个句子)`);
            this.updateProgress(0);

            this.saveCache();

            // 关闭 Transcript 面板
            // NOTE: avoid automatically closing the transcript panel to prevent
            // interfering with page scripts that may expect the panel to exist.
            //this.closeTranscriptPanel();

            // 自动启动滑动窗口后台翻译（在播放时按需预翻译）
            setTimeout(() => {
                this.startSlidingWindowTranslator();
            }, 500);
        }

        // 将连续字幕按句子合并，依据句末标点（. ! ? 。 ！ ？）判断句子结束
        groupSubtitlesIntoSentences() {
            this.sentences = [];
            let current = null;

            for (let i = 0; i < this.subtitles.length; i++) {
                const s = this.subtitles[i];
                const text = (s.en || '').trim();

                if (!current) {
                    current = {
                        text: text,
                        startTime: s.startTime,
                        endTime: s.endTime,
                        indices: [i],
                        cn: null
                    };
                } else {
                    // 合并时保留空格
                    current.text = `${current.text} ${text}`.trim();
                    current.endTime = s.endTime;
                    current.indices.push(i);
                }

                // 如果当前片段以句末标点结尾，则结束该句子
                if (this.isSentenceEnd(text)) {
                    const idx = this.sentences.length;
                    // 如果缓存中已有属于这些字幕的翻译，则使用之作为句子翻译
                    const existingCn = current.indices.map(i => this.subtitles[i].cn).find(c => c);
                    if (existingCn) current.cn = existingCn;
                    this.sentences.push(current);
                    // 标注每个字幕所属的句子索引，并保留已有的字幕翻译
                    current.indices.forEach(si => {
                        this.subtitles[si].sentenceIndex = idx;
                        if (current.cn) this.subtitles[si].cn = current.cn;
                    });
                    current = null;
                }
            }

            // 若有残余未闭合的句子，也添加为一个句子
            if (current) {
                const idx = this.sentences.length;
                const existingCn = current.indices.map(i => this.subtitles[i].cn).find(c => c);
                if (existingCn) current.cn = existingCn;
                this.sentences.push(current);
                current.indices.forEach(si => {
                    this.subtitles[si].sentenceIndex = idx;
                    if (current.cn) this.subtitles[si].cn = current.cn;
                });
                current = null;
            }

            // 若没有任何句子（极端情况），将每条字幕当作独立句子
            if (this.sentences.length === 0 && this.subtitles.length > 0) {
                this.subtitles.forEach((s, i) => {
                    this.sentences.push({ text: s.en, startTime: s.startTime, endTime: s.endTime, indices: [i], cn: s.cn || null });
                    s.sentenceIndex = this.sentences.length - 1;
                });
            }
        }

        isSentenceEnd(text) {
            if (!text) return false;
            // 去掉尾部引号或空白
            const trimmed = text.trim();
            const lastChar = trimmed.charAt(trimmed.length - 1);
            return /[\.\!\?。！？]/.test(lastChar);
        }

        closeTranscriptPanel() {
            // 查找并点击关闭按钮
            const closeButton = Array.from(document.querySelectorAll('button')).find(btn =>
                btn.textContent.includes('Hide transcript panel') ||
                btn.getAttribute('aria-label')?.includes('Hide transcript panel') ||
                btn.querySelector('svg')?.querySelector('path')?.getAttribute('d')?.includes('M6 18L18 6M6 6l12 12') // X 图标
            );

            if (closeButton) {
                console.log('✅ 关闭 Transcript 面板');
                closeButton.click();
            }
        }



        // Sliding-window translator: translate only upcoming subtitles near playback
        startSlidingWindowTranslator() {
            // If already running, don't start another
            if (this.slidingIntervalId) return;

            // If no video element yet, wait and retry
            if (!this.videoElement) {
                // try again shortly
                this.slidingIntervalId = setTimeout(() => {
                    this.slidingIntervalId = null;
                    this.startSlidingWindowTranslator();
                }, 500);
                return;
            }

            this.updateStatus('✅ 滑动窗口翻译已启动（按需预翻译）');

            // Run every 2 seconds to check playback position and prefetch translations
            this.slidingIntervalId = setInterval(async () => {
                if (!this.videoElement) return;

                const currentTime = this.videoElement.currentTime;

                // Find index of current or next sentence
                if (!this.sentences || this.sentences.length === 0) return;

                let currentSentIdx = this.sentences.findIndex(sent => currentTime >= sent.startTime && currentTime < sent.endTime);
                if (currentSentIdx === -1) {
                    // If not on a sentence, find next upcoming sentence
                    currentSentIdx = this.sentences.findIndex(sent => sent.startTime > currentTime);
                    if (currentSentIdx === -1) return; // nothing upcoming
                }

                // Translate a window of upcoming sentences [currentSentIdx, currentSentIdx + windowSize)
                const endSentIdx = Math.min(this.sentences.length, currentSentIdx + this.slidingWindowSize);

                for (let si = currentSentIdx; si < endSentIdx; si++) {
                    const sent = this.sentences[si];
                    if (!sent) continue;
                    if (sent.cn) continue; // already translated

                    try {
                        // mark as translating to avoid duplicate requests
                        sent._translating = true;
                        this.updateStatus(`预翻译句子: ${si + 1}/${this.sentences.length}`);
                        const translation = await this.translateText(sent.text);
                        sent.cn = translation;
                        delete sent._translating;

                        // copy translation to member subtitles
                        if (Array.isArray(sent.indices)) {
                            sent.indices.forEach(idx => {
                                if (this.subtitles[idx]) this.subtitles[idx].cn = sent.cn;
                            });
                        }

                        // save cache after each sentence translation
                        this.saveCache();
                        // small delay between requests to avoid rate limits
                        await this.sleep(this.prefetchDelay);
                    } catch (e) {
                        console.error('滑动窗口句子翻译出错:', e);
                        if (sent) delete sent._translating;
                    }
                }

                // enable toggle if at least one sentence translation is ready
                const anyTranslated = this.sentences.some(s => s.cn);
                if (anyTranslated) {
                    $('#btn-toggle').prop('disabled', false);
                }
            }, 2000);
        }

        stopSlidingWindowTranslator() {
            if (!this.slidingIntervalId) return;
            // slidingIntervalId may be a timeout id or interval id
            clearInterval(this.slidingIntervalId);
            clearTimeout(this.slidingIntervalId);
            this.slidingIntervalId = null;
            this.updateStatus('滑动窗口翻译已停止');
        }

        async translateText(text) {
            try {
                const apiKey = GM_getValue('deepseek_api_key') || '';
                if (!apiKey) {
                    // No API key configured — do not attempt network call
                    this.updateStatus('⚠️ 未配置 DeepSeek API Key，请在设置中填写');
                    console.warn('DeepSeek API key missing; skipping translation.');
                    return text;
                }

                return await new Promise((resolve) => {
                    GM_xmlhttpRequest({
                        method: 'POST',
                        url: 'https://api.deepseek.com/chat/completions',
                        headers: {
                            'Content-Type': 'application/json',
                            'Authorization': 'Bearer ' + apiKey
                        },
                        data: JSON.stringify({
                            model: 'deepseek-chat',
                            messages: [
                                {
                                    role: 'system',
                                    content: "You are a very professional English translation expert. Translate the user's text to Chinese. Only output the translation result, no other text."
                                },
                                {
                                    role: 'user',
                                    content: `把以下内容翻译成中文：${text}`
                                }
                            ],
                            stream: false
                        }),
                        onload: (response) => {
                            try {
                                const data = JSON.parse(response.responseText);
                                if (data.choices && data.choices[0] && data.choices[0].message && data.choices[0].message.content) {
                                    resolve(data.choices[0].message.content);
                                } else {
                                    console.warn('DeepSeek response missing content:', data);
                                    resolve(text); // 翻译失败，返回原文
                                }
                            } catch (e) {
                                console.error('DeepSeek API 错误:', e, response.responseText);
                                resolve(text);
                            }
                        },
                        onerror: (error) => {
                            console.error('DeepSeek API 请求失败:', error);
                            resolve(text);
                        }
                    });
                });
            } catch (error) {
                console.error('翻译失败:', error);
                return text;
            }
        }

        sleep(ms) {
            return new Promise(resolve => setTimeout(resolve, ms));
        }

        toggleDisplay() {
            this.enabled = !this.enabled;

            if (this.enabled) {
                $('#btn-toggle').text('⏸️ 关闭双语显示').removeClass('warning').addClass('danger');
                this.updateStatus('✅ 双语字幕已开启');
                this.startDisplayLoop();
            } else {
                $('#btn-toggle').text('▶️ 开启双语显示').removeClass('danger').addClass('warning');
                this.updateStatus('⏸️ 双语字幕已关闭');
                const ov = document.querySelector('.bilingual-subtitle-overlay');
                if (ov) ov.style.display = 'none';
            }
        }

        startDisplayLoop() {
            if (!this.videoElement) return;

            const checkInterval = setInterval(() => {
                if (!this.enabled) {
                    clearInterval(checkInterval);
                    return;
                }

                const currentTime = this.videoElement.currentTime;
                const currentSub = this.subtitles.find(sub =>
                    currentTime >= sub.startTime && currentTime < sub.endTime
                );

                if (currentSub && currentSub.index !== this.currentIndex) {
                    this.currentIndex = currentSub.index;
                    this.displaySubtitle(currentSub);
                } else if (!currentSub && this.currentIndex !== -1) {
                    this.currentIndex = -1;
                    const overlay = document.querySelector('.bilingual-subtitle-overlay');
                    if (overlay) overlay.style.display = 'none';
                }
            }, 100);
        }

        displaySubtitle(subtitle) {
            const overlay = this.ensureOverlayExists();

            // 找到所属句子并显示句子级原文与翻译（若存在）
            const sentIdx = subtitle.sentenceIndex;
            let cnText = '翻译中...';
            let enText = subtitle.en;
            if (typeof sentIdx !== 'undefined' && this.sentences[sentIdx]) {
                const sent = this.sentences[sentIdx];
                enText = sent.text || subtitle.en;
                cnText = sent.cn || '翻译中...';
            } else if (subtitle.cn) {
                cnText = subtitle.cn;
            }

            // Update inner text without recreating DOM nodes to avoid flicker
            const enDiv = overlay.querySelector('.subtitle-en');
            const cnDiv = overlay.querySelector('.subtitle-cn');
            if (enDiv) enDiv.textContent = enText;
            if (cnDiv) cnDiv.textContent = cnText;

            overlay.style.display = 'block';
        }

        async loadCache() {
            try {
                const cached = GM_getValue('subtitles_cache');
                if (cached) {
                    const data = JSON.parse(cached);
                    if (data.url === window.location.href) {
                        this.subtitles = data.subtitles;
                        console.log('✅ 从缓存加载字幕:', this.subtitles.length);

                        if (this.subtitles.length > 0) {
                            this.onExtractSuccess();
                        }
                    }
                }
            } catch (error) {
                console.error('加载缓存失败:', error);
            }
        }

        saveCache() {
            try {
                const data = {
                    url: window.location.href,
                    subtitles: this.subtitles,
                    timestamp: Date.now()
                };
                GM_setValue('subtitles_cache', JSON.stringify(data));
                console.log('✅ 字幕已缓存');
            } catch (error) {
                console.error('保存缓存失败:', error);
            }
        }

        clearCache() {
            if (confirm('确定要清除缓存吗？')) {
                GM_deleteValue('subtitles_cache');
                this.subtitles = [];
                this.sentences = [];
                this.currentIndex = -1;
                this.enabled = false;

                // 停止滑动窗口翻译器
                this.stopSlidingWindowTranslator();

                $('#subtitle-count').hide();
                $('#btn-toggle').prop('disabled', true).text('▶️ 开启双语显示').removeClass('danger').addClass('warning');
                const ov = document.querySelector('.bilingual-subtitle-overlay');
                if (ov) ov.style.display = 'none';

                this.updateStatus('✅ 缓存已清除');
                this.updateProgress(0);
            }
        }
    }

    // 启动
    $(document).ready(() => {
        try {
            console.log('📌 DOM 已加载，初始化字幕翻译器...');
            const extractor = new TranscriptPanelExtractor();
            window.DL_SUBTITLE_EXTRACTOR = extractor; // 保存到全局变量方便调试
            console.log('✅ 字幕翻译器初始化完成');
            console.log('💡 提示: 在控制台输入 DL_SUBTITLE_EXTRACTOR.extractSubtitles() 可手动提取字幕');
            console.log('💡 提示: 在控制台输入 DL_SUBTITLE_EXTRACTOR.showDebugInfo() 可查看调试信息');
        } catch (error) {
            console.error('❌ 字幕翻译器初始化失败:', error);
        }
    });

})();
